package com.yunc.nio.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * 1、支持多客户端通过nio连接到服务端
 * 2、客户端登录号，发送已经设置好的信息及在线人数给客户端，并通知其他客户端
 *
 * @author taocongcong
 * @create 2019-03-11 11:33
 */
public class NIOServer {

    private int port = 8080;
    private Charset charset = Charset.forName("UTF-8");

    /**
     * 用来记录昵称
     */
    private static HashSet<String> users = new HashSet<>();

    private Selector selector = null;

    public NIOServer(int port) throws IOException {
        this.port = port;
        // 先打开通道
        ServerSocketChannel server = ServerSocketChannel.open();
        // 设置关卡
        server.bind(new InetSocketAddress(this.port));
        // 设置为非阻塞，默认为阻塞，用来向下兼容bio
        server.configureBlocking(false);
        // 服务大厅开始工作
        selector = Selector.open();
        // 事件,为这个通道监听ACCEPT事件
        server.register(selector, SelectionKey.OP_ACCEPT);
        System.out.print("服务已启动，监听端口为：" + this.port);
    }

    /**
     * 与bio不同
     * 1、bio开启多线程，等待事件结束
     * 2、nio单线程，将所有事件引入到服务大厅，selector
     */
    public void listener() throws IOException {
        // 这里不会阻塞，只有当有事件才会进入
        while (true) {
            // 轮询，共有多少人排队
            int wait = selector.select();
            // 没有事件
            if (wait == 0) {
                continue;
            }
            // 需要处理事件数
            Set<SelectionKey> keys = selector.selectedKeys();
            // 便利
            Iterator<SelectionKey> iterator = keys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // 处理一个，移除
                iterator.remove();
                // 处理逻辑
                process(key);
            }
        }
    }

    public void process(SelectionKey key) throws IOException {
        // 判断客户端确定已经进入服务大厅，并且已经可以实现交互
        if (key.isAcceptable()) {
            ServerSocketChannel server = (ServerSocketChannel)key.channel();
            SocketChannel client = server.accept();
            // 设置为非阻塞模式
            client.configureBlocking(false);
            // 注册选择器，设置为读取默认，收到一个连接请求，注册到selector
            // 之后连接的这个数据，就有这个SocketChannel处理
            client.register(selector, SelectionKey.OP_READ);

            // 将其对应的channel设置为准备接受其他客户端请求
            key.interestOps(SelectionKey.OP_ACCEPT);
            System.out.print("有客户端连接");

            client.write(charset.encode("请输入你的昵称"));
        }

        // 处理来自客户端的数据读取请求
        StringBuilder content = new StringBuilder();
        if (key.isReadable()) {
            SocketChannel client = (SocketChannel) key.channel();
            // 往缓冲区读数据
            ByteBuffer buff = ByteBuffer.allocate(1024);
            while (client.read(buff) > 0) {
                buff.flip();
                content.append(charset.decode(buff));
            }
            // 将key设置为准备下一次接受数据
            key.interestOps(SelectionKey.OP_READ);
            if (content.length() > 0) {
                // 不用json，直接用字符串切割
                String[] contentArr = content.toString().split("#@#");
                // 注册
                if (contentArr != null && contentArr.length == 1) {
                    String nickName = contentArr[0];
                    if (users.contains(nickName)) {
                        client.write(charset.encode("你的昵称已存在"));
                    } else {
                        users.add(nickName);
                        int onlineCount = onlineCount();
                        String message = "欢迎" + nickName + "进入聊天室！当前人数：" + onlineCount;
                        // 发送信息给所有用户
                        broadCast(null, message);
                    }
                // 如果已注册，发送信息
                } else if (contentArr != null && contentArr.length > 1){
                    String nickName = contentArr[0];
                    String message = nickName + "说：" + contentArr[1];
                    if (users.contains(nickName)) {
                        broadCast(client, message);
                    }
                }
            }
        }

    }

    private void broadCast(SocketChannel client, String message) throws IOException {
        for (SelectionKey key : selector.keys()) {
            Channel targetChannel = key.channel();
            if (targetChannel instanceof SocketChannel && targetChannel != client) {
                SocketChannel target = (SocketChannel) targetChannel;
                target.write(charset.encode(message));
            }
        }
    }

    private int onlineCount() {
        int res = 0;
        for (SelectionKey key : selector.keys()) {
            Channel target = key.channel();
            if (target instanceof SocketChannel) {
                res++;
            }

        }
        return res;
    }

    public static void main(String[] args) throws IOException {
        new NIOServer(8080).listener();
    }
}
